import 'package:escooter_patcher_flutter/base/base.dart';
import 'package:escooter_patcher_flutter/ble/ble_plugin.dart';
import 'package:escooter_patcher_flutter/prompt.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:version/version.dart';

class OperationRSController extends BaseController {
  List<String> regions = [
    "Europe(25km/h)",
    "WorldWide(General)",
    "Other"
  ];

  static List<int> getSnBytes = [0xaa, 0xaa, 0x11, 0x02, 0x02, 0x02, 0x13];
  static List<int> getModelBytes = [0xaa, 0xaa, 0x11, 0x02, 0x02, 0x01, 0x10];
  static List<int> getVersionBytes = [0xaa, 0xaa, 0x11, 0x02, 0x02, 0x06, 0x17];
  static List<int> getRegionBytes = [
    0xaa,
    0xaa,
    0x13,
    0x05,
    0x01,
    0x32,
    0x01,
    0x05,
    0x00,
    0x21
  ];

  // 6,25,25,25
  static List<String> setDesignedMaxSpeedEurope = [
    "aaaa13080131018500025802f7",
    "aaaa1308013101850102c40961",
    "aaaa1308013101850202c40962",
    "aaaa1308013101850302c40963",
  ];

  // 32, 64,100,130
  static List<String> setDesignedMaxSpeedWorldWide = [
    "aaaa1308013101850002800c21",
    "aaaa13080131018501020019b5",
    "aaaa1308013101850202102798",
    "aaaa1308013101850302c83254",
  ];

  // 25, 65,90,100
  static List<String> setDesignedMaxSpeedOther= [
    "aaaa1308013101850002c40960",
    "aaaa13080131018501026419d1",
    "aaaa13080131018502022823a4",
    "aaaa1308013101850302102799",
  ];

  var currentModel = unknownModel.obs;
  var sn = "--".obs;
  var version = "--".obs;
  var currentRegion = "--".obs;

  bool donNotSupportChangeRegion = false;

  final BleManager _bleManager = BleManager();

  SharedPreferences? _prefs;

  @override
  void init() async {
    _bleManager.onNotifiedCallback = (ActiveDevice device, List<int> values) =>
        onBleDataReceived(device, values);

    _prefs = await SharedPreferences.getInstance();

    getInfo();

    if (await shouldAutoShowDisclaimerSignInfo()) {
      showDisclaimerDialog();
    }
  }

  /// 简单处理BLE回复数据
  void onBleDataReceived(ActiveDevice device, List<int> values) {
    var format = values[2];
    int channel;
    int property;
    var one2one = (format & 0x03) != 0x03;
    if (one2one) {
      // 一对一
      channel = (values[4] & 0x0F);
      property = values[5];
    } else {
      // 非一对一
      channel = (values[6] & 0x0F);
      property = values[7];
    }

    int paramOffset = 6;
    if (!one2one) paramOffset = 8;

    // print("received bytes: " + Bytes.bytes2HexString(values, offset: 0, length: values.length));
    // print("onBleDataReceived, channel: $channel, property: $property, param offset: $paramOffset");

    if (channel == 2 && property == 1) {
      // model
      String hex = Bytes.bytes2HexString(values, offset: paramOffset, length: 3)
          .toUpperCase();

      currentModel.value = VehicleModel.fromHex(hex);
    } else if (channel == 2 && property == 2) {
      // sn
      if (values.length < 10 || values[paramOffset] == 0) {
        sn.value = "0000000000000000";
      } else {
        sn.value = Bytes.bytes2String(values, offset: paramOffset, length: 16);
      }
    } else if (channel == 2 && property == 9) {
      // rewrite designed max speed response
      if (values[paramOffset] == 0) {
      } else {
        toast("Failed!");
      }
    } else if (channel == 2 && property == 6) {
      // version
      int offset = paramOffset;

      // 版本号数量
      int count = (values.length - offset) ~/ (1 + 4 + 4);

      String versionInfo = "";
      for (int i = 0; i < count; i++) {
        int device = values[offset];

        offset++;
        String appVersion = formatVersion(values, offset, 4);
        offset += 4;
        offset += 4;

        String deviceName = getDeviceName(device);
        versionInfo += deviceName;
        versionInfo += ": ";
        versionInfo += appVersion;
        versionInfo += "\n";
      }
      if (versionInfo.length > 2) {
        version.value = versionInfo.substring(0, versionInfo.length - 1);
      }
    } else if (channel == 1 && property == 5 && values[8] == 0) {
      // 获取第#0档的max speed
      int speed = Bytes.bytes2UInt16LE(values, paramOffset + 2);
      currentRegion.value =
          firstClassMaxSpeed2Region(currentModel.value, speed);
    } else if (channel == 0x1 && property == 0x85 && values[8] == 2) {
      // write response
      if (values[9] == 0) {
        // success
        toast("Succeed");
      }
    } else if (channel == 1 &&
        property == (5 | 0x80) &&
        values[paramOffset + 2] == 2) {
      // 测试通道写第三档速度回复
    }
  }

  void disconnectCurrent({String? flag}) {
    BleManager().disconnect(flag: flag);
  }

  /// 获取车辆全部信息
  void getInfo() {
    showProgress("Loading...");
    Future.delayed(const Duration(milliseconds: 100), () {
      _bleManager.write(getSnBytes);
    }).then((nothing) {
      Future.delayed(const Duration(milliseconds: 500), () {
        _bleManager.write(getModelBytes);
      }).then((value) {
        Future.delayed(const Duration(milliseconds: 500), () {
          _bleManager.write(getVersionBytes);
        }).then((value) {
          Future.delayed(const Duration(milliseconds: 500), () {
            _bleManager.write(getRegionBytes);
          }).then((value) {
            dismissProgress();
          });
        });
      });
    });
  }

  @override
  void onClose() {
    super.onClose();
    BleManager().disconnect();
  }

  void toast(String msg) {
    Prompt.toast(msg);
  }

  /// 格式化版本号
  static String formatVersion(List<int> data, int offset, int length) {
    if (data.isEmpty || data.length < offset + length) {
      return "--";
    }
    return "${data[offset + 2 + 1] & 0xFF}.${data[offset + 2] & 0xFF}.${Bytes.bytes2UInt16LE(data, offset)}";
  }

  /// 获取设备号名
  static String getDeviceName(int device) {
    if (device == 0x31) return "Display Board";
    if (device == 0x32) return "Driver Board#1";
    if (device == 0x33) return "Driver Board#2";
    if (device == 0x34) return "BMS";
    return "Unknown";
  }

  /// 根据第#0档最大设计速度获取显示的地区名
  String firstClassMaxSpeed2Region(VehicleModel model, int maxSpeed) {
    if (model == unknownModel) return "Unknown";
    if (maxSpeed == 600) return regions[0];
    if (maxSpeed == 3200) return regions[1];
    if (maxSpeed == 2500) return regions[2];
    return "Unknown";
  }

  int lastClickRegionIndex = -1;

  void changeVehicleRegion(int regionIndex) {
    List<String> commands = [];
    switch (regionIndex) {
      case 0:
        commands = setDesignedMaxSpeedEurope;
        break;
      case 1:
        commands = setDesignedMaxSpeedWorldWide;
        break;
      case 2:
        commands = setDesignedMaxSpeedOther;
        break;
    }
    if (commands.isEmpty) {
      toast("Error region, please restart this APP to re-change");
      return;
    }
    List<int> bytes = [];

    commands.forEach((hexBytes) {
      bytes.addAll(Bytes.hexString2Bytes(hexBytes));
    });
    _bleManager.write(bytes);

    showProgress("Changing...");
    lastClickRegionIndex = regionIndex;
    Future.delayed(const Duration(milliseconds: 500), () {
      _bleManager.write(getRegionBytes);
      lastClickRegionIndex = -1;
      dismissProgress();
    });
  }

  showDisclaimerDialog() {
    Prompt.showMessageDialog(
        "The software is provided as 'as is', without any warranty of any kind.\nThe developer shall not be responsible for any claim or damage that might arise while using this software.\nThe user himself is responsible for the correct usage and shall act with caution to prevent any damage of his own possessions or of others.\nPlease check local laws before riding your modded scooter on a public place.\nIn countries such as Germany and many others, modifying your scooter might void its permit",
        confirm: "I Agree", onConfirm: () {
      saveDisclaimerSignInfo();
    });
  }

  void saveDisclaimerSignInfo() async {
    List<String>? history = _prefs!.getStringList("sign_history");
    history ??= [];
    history.add(DateTime.now().millisecondsSinceEpoch.toString());

    _prefs!.setStringList("sign_history", history);
    _prefs!
        .setInt("latest_sign_timestamp", DateTime.now().millisecondsSinceEpoch);
  }

  Future<bool> shouldAutoShowDisclaimerSignInfo() async {
    int? timestamp = _prefs!.getInt("latest_sign_timestamp");
    return timestamp == null || timestamp == 0;
  }

  showProgress(String msg) {
    EasyLoading.show(status: msg);
  }

  dismissProgress() {
    EasyLoading.dismiss();
  }

  static bool v1NotLowerThanV2(String v1, String v2) {
    return Version.parse(v1) >= Version.parse(v2);
  }
}

var unknownModel = VehicleModel("", "Unknown");
var inmotionRS = VehicleModel("0309", "INMOTION RS");

class VehicleModel {
  String displayName;
  String hexCodePrefix;

  VehicleModel(this.hexCodePrefix, this.displayName);

  @override
  String toString() {
    return displayName;
  }

  static VehicleModel fromHex(String hex) {
    print("model hex: " + hex);
    if (hex.isEmpty) return unknownModel;
    if (hex.startsWith(inmotionRS.hexCodePrefix)) return inmotionRS;
    return unknownModel;
  }
}

class Bytes {
  static String bytes2String(List<int> bytes,
      {int offset = 0, int length = 0}) {
    return String.fromCharCodes(bytes, offset, offset + length);
  }

  static String bytes2HexString(List<int> bytes,
      {int offset = 0, int length = 0}) {
    String str = "";
    for (var i = offset; i < length + offset; i++) {
      str += ((bytes[i] < 0x10 ? "0" : "") + bytes[i].toRadixString(16));
    }
    return str;
  }

  static int bytes2UInt16LE(List<int> bytes, [int offset = 0]) {
    return (bytes[offset + 1] << 8) | bytes[offset];
  }

  static List<int> hexString2Bytes(String hexString) {
    int len = hexString.length;
    List<int> bytes = [];
    for (int i = 0; i < len; i += 2) {
      bytes.add(hexStr2Byte(hexString.substring(i, i + 2)));
    }
    return bytes;
  }

  static int hexStr2Byte(String str, [int offset = 0, int length = 2]) {
    return toUint8(
        int.parse(str.substring(offset, offset + length), radix: 16));
  }

  static int toUint8(int i) {
    i &= 0xFF;
    if (i <= -0x100 ~/ 2) i = 0x100 + i;
    return i;
  }
}
